﻿using System;
using System.Collections.Generic;
using System.IO;
using ParserGenerator.Collections;
using ParserGenerator.Extensions;
using ParserGenerator.GrammarConstruction;
using ParserGenerator.Input;
using ParserGenerator.Output;
using ParserGenerator.ScannerConstruction;
using ParserGenerator.SyntacticAnalysis;

namespace ParserGenerator.CodeGeneration
{
    [Serializable]
    public class CSharpLexicalActionWriter : CSharpSemanticActionWriter
    {
        public Identifier StartNumberName { get; }
        public Identifier RuleNumberName { get; }

        public GrammarTerminalTable Tokens { get; set; }
        public ScannerAutomatonStartTable Starts { get; set; }
        public bool NeedBreak { get; set; }

        internal CSharpLexicalActionWriter(GrammarDeclaration grammar)
            : base(grammar)
        {
            if (grammar == null)
                throw new ArgumentNullException(nameof(grammar));
            StartNumberName = Identifier.New(grammar.GetOptionValue("pg.scanner.start.name", "start"));
            RuleNumberName = Identifier.New(grammar.GetOptionValue("pg.scanner.rule.name", "rule"));
        }

        public virtual void WriteReturnOperand(Expression n, IndentedWriter writer)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            if (n.IsNameReference && Tokens != null)
            {
                GrammarTerminalSymbol sym;
                if (Tokens.NamedTokens.TryGetValue(n.AsNameReference.Name.Name, out sym))
                {
                    writer.Write(sym.Number);
                    return;
                }
            }
            if (n.IsQuoteString && Tokens != null)
            {
                GrammarTerminalSymbol sym;
                if (Tokens.LiteralTokens.TryGetValue(n.AsQuoteString.Value.Value, out sym))
                {
                    writer.Write(sym.Number);
                    return;
                }
            }
            base.WriteExpression(n, writer);
        }

        public override void WriteExpression(Expression n, IndentedWriter writer, int d)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            if (n.IsNameReference && Starts != null)
            {
                ScannerAutomatonStart start;
                if (Starts.TryGetValue(n.AsNameReference.Name.Name, out start))
                {
                    writer.Write(start.Number);
                    return;
                }
            }
            base.WriteExpression(n, writer, d);
        }

        public override void WriteSemanticAction(SemanticAction n, IndentedWriter writer)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            SourceContext context = n.Context;
            if (context != null)
            {
                int lineno = context.Begin.Line + 1;
                string path = Path.GetFullPath(context.Document.FullName);
                writer.WriteLine("// #line {0} \"{1}\"", lineno, path);
            }
            base.WriteSemanticAction(n, writer);
            if (context != null)
                writer.WriteLine("// #line default");
        }

        public override void WriteStatement(Statement n, IndentedWriter writer)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            if (n.IsExpr && n.AsExpr.Value.IsYield && n.AsExpr.Value.AsYield.Value != null)
            {
                writer.Write("return ");
                WriteReturnOperand(n.AsExpr.Value.AsYield.Value, writer);
                writer.WriteLine(";");
                NeedBreak = false;
            }
            else if (n.IsReturn && n.AsReturn.Value != null)
            {
                writer.Write("return ");
                WriteReturnOperand(n.AsReturn.Value, writer);
                writer.WriteLine(";");
                NeedBreak = false;
            }
            else
            {
                base.WriteStatement(n, writer);
                if (n.IsThrow && n.AsThrow.Exception != null)
                    NeedBreak = false;
            }
        }

        public virtual void WriteStatementList(Sequence<Statement> statements, IndentedWriter writer)
        {
            if (statements == null)
                throw new ArgumentNullException(nameof(statements));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            foreach (Statement statement in statements)
                WriteStatement(statement, writer);
        }

        public override void WriteFastSemanticAction(FastSemanticAction n, IndentedWriter writer)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            writer.Write("return ");
            if (n.Value.IsYield && n.Value.AsYield.Value != null)
                WriteReturnOperand(n.Value.AsYield.Value, writer);
            else
                WriteReturnOperand(n.Value, writer);
            writer.WriteLine(";");
            NeedBreak = false;
        }

        public override void WriteFullSemanticAction(FullSemanticAction n, IndentedWriter writer)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            WriteStatement(n.Actions, writer);
        }

        public virtual void WriteStartActionSwitch(ScannerAutomaton scanner, IndentedWriter writer)
        {
            if (scanner == null)
                throw new ArgumentNullException(nameof(scanner));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            writer.Write("switch (");
            WriteIdentifier(StartNumberName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            var dict = new Dictionary<SemanticAction, List<int>>();
            foreach (ScannerAutomatonStart start in scanner.Starts.Values)
                if (start.DefaultAction != null)
                    dict.AddValueItem(start.DefaultAction, start.Number);
            foreach (var t in dict)
            {
                foreach (int num in t.Value)
                    writer.WriteLine("case {0}:", num);
                writer.OpenBlock();
                Tokens = scanner.Tokens;
                Starts = scanner.Starts;
                WriteSemanticAction(t.Key, writer);
                Tokens = null;
                Starts = null;
                writer.CloseBlock();
            }
            writer.WriteLine("default:");
            writer.OpenBlock();
            writer.Write("throw SyntaxError(");
            if (UseStringResources)
            {
                if (UseFullyQualifiedNamespace)
                {
                    writer.Write("global::");
                    WriteDottedName(StringResourcesNamespace, writer);
                    writer.Write(".");
                }
                WriteIdentifier(StringResourcesClassName, writer);
                writer.Write(".");
                WriteIdentifier(InvalidTokenResourceName, writer);
            }
            else
            {
                WriteStringLiteral(InvalidTokenResourceText, writer);
            }
            writer.WriteLine(");");
            writer.CloseBlock();
            writer.CloseCurlyBlock();
        }

        public virtual void WriteRuleActionSwitch(ScannerAutomaton scanner, IndentedWriter writer)
        {
            if (scanner == null)
                throw new ArgumentNullException(nameof(scanner));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            writer.Write("switch (");
            WriteIdentifier(RuleNumberName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            foreach (var t in scanner.States.Reductions)
            {
                GrammarTerminalSymbol sym = scanner.Tokens.TryGetToken(t.Key);
                if (sym == null)
                {
                    writer.WriteLine("case {0}:", t.Value);
                    writer.OpenBlock();
                    writer.WriteLine("// {0}", t.Key.WithAction(null));
                    NeedBreak = true;
                    if (t.Key.Action != null)
                    {
                        Tokens = scanner.Tokens;
                        Starts = scanner.Starts;
                        WriteSemanticAction(t.Key.Action, writer);
                        Tokens = null;
                        Starts = null;
                    }
                    if (NeedBreak)
                        writer.WriteLine("break;");
                    NeedBreak = false;
                    writer.CloseBlock();
                }
            }
            writer.WriteLine("default:");
            writer.OpenBlock();
            writer.WriteLine("break;");
            writer.CloseBlock();
            writer.CloseCurlyBlock();
        }
    }
}
